﻿using HCQ2_Common.Log;
using Newtonsoft.Json;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Serialization;

namespace HCQ2_Common
{
    /// <summary>
    /// 微信支付，这里只有 NATIVE 微信扫码支付，其他支付方式在支付项目中实现
    /// </summary>
    public class WechatPayHelper
    {
        /// <summary>
        /// 接口地址
        /// </summary>
        private const string url = "https://api.mch.weixin.qq.com/pay/unifiedorder";

        public WechatPayResult<AppPayOutput> Pay(AppPayInput input) 
        {
            if (input == null) throw new ArgumentNullException(nameof(input));

            if (input.TotalFee <= 0) {
                return new WechatPayResult<AppPayOutput>()
                {
                    success = false,
                    message = "金额不能小于0"
                };
            }

            if (string.IsNullOrWhiteSpace(input.Body)) {
                return new WechatPayResult<AppPayOutput>()
                {
                    success = false,
                    message = "商品描述必须填写"
                };
            }

            if (string.IsNullOrWhiteSpace(input.SpbillCreateIp)) {
                return new WechatPayResult<AppPayOutput>()
                {
                    success = false,
                    message = "终端IP必须填写"
                };
            }

            WeChatPayConfig config = GetConfig();

            var model = new WechatPayNativeRequest
            {
                AppId = config.PayAppId,
                MchId = config.MchId,
                Attach = input.Attach,
                Body = input.Body,
                Detail = input.Detail,
                FeeType = input.FeeType,
                GoodsTag = input.GoodsTag,
                LimitPay = input.LimitPay,
                OutTradeNo = input.OutTradeNo,
                SpbillCreateIp = input.SpbillCreateIp,
                TimeExpire = input.TimeExpire,
                TimeStart = input.TimeStart,
                TotalFee = ((int)(input.TotalFee * 100)).ToString(),
                NonceStr = Guid.NewGuid().ToString("N"),
                NotifyUrl = config.PayNotifyUrl
            };
            model.Sign = CreateSign(model);

            var result = PostXML(url, model);
            LogHelper.InfoLog("微信扫码支付请求返回信息：" + JsonHelper.DataContractJsonSerialize(result));
            if (result.IsSuccess())
            {
                var data = new
                {
                    appid = result.AppId,
                    noncestr = result.NonceStr,
                    partnerid = result.Mch_Id,
                    prepayid = result.PrepayId,
                    package = "Sign=WXPay",
                    timestamp = GetTimestamp()
                };

                var data2 = new AppPayOutput
                {
                    CodeUrl = result.CodeUrl,
                    AppId = data.appid,
                    MchId = model.MchId,
                    PrepayId = result.PrepayId,
                    NonceStr = data.noncestr,
                    PaySign = CreateMd5Sign(GetDictionaryByType(data), config.TenPayKey),
                    SignType = "MD5",
                    TimeStamp = data.timestamp,
                    Package = data.package
                };
                return new WechatPayResult<AppPayOutput>()
                {
                    success = true,
                    message = "调用微信接口成功",
                    data = data2
                };               
            }

            LogHelper.ErrorLog("微信扫码支付错误：" + result.GetFriendlyMessage());
            
            return new WechatPayResult<AppPayOutput>()
            {
                success = false,
                message = "支付错误，请联系客服或重新支付，微信返回错误信息："+ result.GetFriendlyMessage()
            };
        }

        /// <summary>
        ///     获取时间戳
        /// </summary>
        /// <returns></returns>
        private string GetTimestamp()
        {
            var ts = DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0);
            return Convert.ToInt64(ts.TotalSeconds).ToString();
        }
        /// <summary>
        ///     POST提交请求，返回ApiResult对象
        /// </summary>
        /// <typeparam name="T">ApiResult对象</typeparam>
        /// <param name="url">请求地址</param>
        /// <param name="obj">提交的数据对象</param>
        /// <returns>ApiResult对象</returns>
        private UnifiedorderResult PostXML(string url, object obj, Func<string, string> serializeStrFunc = null)            
        {
            WebRequestHelper wr = new WebRequestHelper();
            string resultStr = null;
            UnifiedorderResult result = wr.HttpPost(url, obj, out resultStr, serializeStrFunc,
                WebRequestDataTypes.XML, WebRequestDataTypes.XML);
            if (result != null)
                result.DetailResult = resultStr;
            return result;
        }

        /// <summary>
        /// 签名
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="model"></param>
        /// <returns></returns>
        private string CreateSign<T>(T model)
        {
            var config = GetConfig();
            var dictionary = GetDictionaryByType(model);
            //生成Sign
            return CreateMd5Sign(dictionary, config.TenPayKey); 
        }

        /// <summary>
        /// 循环获取一个实体类每个字段的XmlAttribute属性的值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="model"></param>
        /// <returns></returns>
        private Dictionary<string, string> GetDictionaryByType<T>(T model)
        {
            var dict = new Dictionary<string, string>();

            var type = model.GetType(); //获取类型

            var props = typeof(T).GetProperties();
            foreach (var prop in props)
            {
                var attrs = prop.GetCustomAttributes(true);
                XmlElementAttribute attr = null;
                if (attrs.Length > 0) attr = attrs[0] as XmlElementAttribute;
                var property = type.GetProperty(prop.Name);
                var value = property.GetValue(model, null); //获取属性值
                dict.Add(attr?.ElementName ?? property.Name, value?.ToString());
            }

            return dict;
        }

        /// <summary>
        /// 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名
        /// </summary>
        /// <param name="value">参数值</param>
        /// <returns></returns>
        private string CreateMd5Sign(Dictionary<string, string> dict, string value)
        {
            var akeys = new ArrayList();
            foreach (var x in dict)
            {
                if ("sign".CompareTo(x.Key) == 0)
                    continue;
                akeys.Add(x.Key);
            }

            var sb = new StringBuilder();
            akeys.Sort();

            foreach (string k in akeys)
            {
                var v = dict[k];
                if (null != v && "".CompareTo(v) != 0
                              && "sign".CompareTo(k) != 0 && "key".CompareTo(k) != 0)
                    sb.Append(k + "=" + v + "&");
            }

            sb.Append("key=" + value);
            var md5 = MD5.Create();
            var bs = md5.ComputeHash(Encoding.UTF8.GetBytes(sb.ToString()));
            var sbuilder = new StringBuilder();
            foreach (var b in bs)
                sbuilder.Append(b.ToString("x2"));
            //所有字符转为大写
            return sbuilder.ToString().ToUpper();
        }

        /// <summary>
        ///     获取支付配置
        /// </summary>
        /// <returns></returns>
        private static WeChatPayConfig GetConfig()
        {
            var config = WeChatPayConfig.weChatPayConfig;
            if (config == null) throw new ArgumentNullException("请配置支付配置信息!", "PayConfig");

            if (string.IsNullOrWhiteSpace(config.PayAppId))
                throw new ArgumentNullException("PayAppId必须配置!", nameof(config.PayAppId));

            if (string.IsNullOrWhiteSpace(config.MchId))
                throw new ArgumentNullException("MchId(商户Id)必须配置!", nameof(config.MchId));

            if (string.IsNullOrWhiteSpace(config.PayNotifyUrl))
                throw new ArgumentNullException("PayNotifyUrl(支付回调地址)必须配置!", nameof(config.PayNotifyUrl));

            if (string.IsNullOrWhiteSpace(config.TenPayKey))
                throw new ArgumentNullException("TenPayKey(支付密钥)必须配置!", nameof(config.TenPayKey));

            return config;
        }
    }

    /// <summary>
    /// 微信扫码支付返回结果体
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class WechatPayResult<T> 
    {
        /// <summary>
        /// 是否成功
        /// </summary>
        public bool success;

        /// <summary>
        /// 数据
        /// </summary>
        public T data;

        /// <summary>
        /// 消息
        /// </summary>
        public string message;
    }

    /// <summary>
    /// 支付方法参数对象
    /// </summary>
    public class AppPayInput
    {
        /// <summary>
        ///     商品描述。示例：腾讯充值中心-QQ会员充值
        /// </summary>
        public string Body { get; set; }

        /// <summary>
        ///     商品名称明细列表-可控
        /// </summary>
        public string Detail { get; set; }

        /// <summary>
        ///     附加数据，在查询API和支付通知中原样返回，该字段主要用于商户携带订单的自定义数据-可空
        /// </summary>
        public string Attach { get; set; }

        /// <summary>
        ///     商户系统内部的订单号,32个字符内、可包含字母 为空则自动生成
        /// </summary>
        public string OutTradeNo { get; set; }

        /// <summary>
        ///     符合ISO 4217标准的三位字母代码，默认人民币：CNY
        /// </summary>
        public string FeeType { get; set; } = "CNY";

        /// <summary>
        ///     订单总金额，单位为元
        /// </summary>
        public decimal TotalFee { get; set; }

        /// <summary>
        ///     用户端实际ip
        /// </summary>
        public string SpbillCreateIp { get; set; }

        /// <summary>
        ///     订单生成时间，格式为yyyyMMddHHmmss，如2009年12月25日9点10分10秒表示为20091225091010。
        /// </summary>
        public string TimeStart { get; set; }

        /// <summary>
        ///     订单失效时间，格式为yyyyMMddHHmmss，如2009年12月27日9点10分10秒表示为20091227091010
        /// </summary>
        public string TimeExpire { get; set; }

        /// <summary>
        ///     商品标记，代金券或立减优惠功能的参数，说明详见代金券或立减优惠-非必填
        /// </summary>
        public string GoodsTag { get; set; }

        /// <summary>
        ///     no_credit--指定不能使用信用卡支付
        /// </summary>
        public string LimitPay { get; set; } = "no_credit";
    }

    /// <summary>
    /// 微信扫码支付调用微信接口参数对象，来源：https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=9_1
    /// </summary>
    public class WechatPayNativeRequest 
    {
        /// <summary>
        /// 公众账号ID      微信支付分配的公众账号ID（企业号corpid即为此appid）
        /// </summary>
        [XmlElement("appid")]
        public string AppId { get; set; }

        /// <summary>
        ///     微信支付分配的商户号
        /// </summary>
        [XmlElement("mch_id")]
        public string MchId { get; set; }

        /// <summary>
        ///     随机字符串，不长于32位
        /// </summary>
        [XmlElement("nonce_str")]
        public string NonceStr { get; set; }

        /// <summary>
        ///     签名
        /// </summary>
        [XmlElement("sign")]
        public string Sign { get; set; }

        /// <summary>
        ///     商品描述，示例：腾讯充值中心-QQ会员充值
        /// </summary>
        [XmlElement("body")]
        public string Body { get; set; }

        /// <summary>
        ///     商品名称明细列表-非必填
        /// </summary>
        [XmlElement("detail")]
        public string Detail { get; set; }

        /// <summary>
        ///     附加数据，在查询API和支付通知中原样返回，该字段主要用于商户携带订单的自定义数据-非必填
        /// </summary>
        [XmlElement("attach")]
        public string Attach { get; set; }

        /// <summary>
        ///     商户系统内部的订单号,32个字符内、可包含字母, 其他说明见商户订单号，系统内部的订单号
        /// </summary>
        [XmlElement("out_trade_no")]
        public string OutTradeNo { get; set; }

        /// <summary>
        ///     符合ISO 4217标准的三位字母代码，默认人民币：CNY -非必填
        /// </summary>
        [XmlElement("fee_type")]
        public string FeeType { get; set; }

        /// <summary>
        ///     订单总金额，单位为分
        /// </summary>
        [XmlElement("total_fee")]
        public string TotalFee { get; set; }

        /// <summary>
        ///     终端IP
        /// </summary>
        [XmlElement("spbill_create_ip")]
        public string SpbillCreateIp { get; set; }

        /// <summary>
        ///     订单生成时间，格式为yyyyMMddHHmmss，如2009年12月25日9点10分10秒表示为20091225091010。
        /// </summary>
        [XmlElement("time_start")]
        public string TimeStart { get; set; }

        /// <summary>
        ///     订单失效时间，格式为yyyyMMddHHmmss，如2009年12月27日9点10分10秒表示为20091227091010
        /// </summary>
        [XmlElement("time_expire")]
        public string TimeExpire { get; set; }

        /// <summary>
        ///     商品标记，代金券或立减优惠功能的参数，说明详见代金券或立减优惠-非必填
        /// </summary>
        [XmlElement("goods_tag")]
        public string GoodsTag { get; set; }

        /// <summary>
        /// 接收微信支付异步通知回调地址，通知url必须为直接可访问的url，不能携带参数
        /// </summary>
        [XmlElement("notify_url")]
        public string NotifyUrl { get; set; }

        /// <summary>
        ///     支付类型（JSAPI，NATIVE，APP）公众号内支付填JSAPI，当前只用 NATIVE 支付
        /// </summary>
        [XmlElement("trade_type")]
        public string TradeType { get; set; } = "NATIVE";

        /// <summary>
        ///     no_credit--指定不能使用信用卡支付
        /// </summary>
        [XmlElement("limit_pay")]
        public string LimitPay { get; set; }
    }

    /// <summary>
    /// 支付配置
    /// </summary>
    public class WeChatPayConfig
    {
        /// <summary>
        ///     APPId
        /// </summary>
        public string PayAppId { get; set; }

        /// <summary>
        ///     商户Id
        /// </summary>
        public string MchId { get; set; }

        /// <summary>
        ///     支付回调路径
        /// </summary>
        public string PayNotifyUrl { get; set; }

        /// <summary>
        ///     支付密钥
        /// </summary>
        public string TenPayKey { get; set; }
        /// <summary>
        /// 商户证书相对路径
        /// </summary>
        public string PayCertPath { get; set; }
        /// <summary>
        /// 证书密钥（商户id）
        /// </summary>
        public string CertPassword { get; set; }

        /// <summary>
        /// 支付配置
        /// </summary>
        public static WeChatPayConfig weChatPayConfig;
        static WeChatPayConfig() {
            weChatPayConfig = new WeChatPayConfig()
            {
                PayAppId = AppConfigAppSetting.GetName("PayAppId"),
                MchId = AppConfigAppSetting.GetName("MchId"),
                PayNotifyUrl = AppConfigAppSetting.GetName("PayNotifyUrl"),
                TenPayKey = AppConfigAppSetting.GetName("TenPayKey"),
                PayCertPath = AppConfigAppSetting.GetName("PayCertPath"),
                CertPassword = AppConfigAppSetting.GetName("CertPassword"),
            };
        }
    }

    public class WebRequestHelper
    {

        /// <summary>
        ///     代理密码
        /// </summary>
        public string ProxyKey { get; set; }

        /// <summary>
        ///     代理地址
        /// </summary>
        public string ProxyAddress { get; set; }


        /// <summary>
        ///     代理用户
        /// </summary>
        public string ProxyUser { get; set; }

        public CookieContainer Cookie { get; set; }

        /// <summary>
        ///     是否允许重定向(默认:true)
        /// </summary>
        public bool AllowAutoRedirect { get; set; } = true;

        /// <summary>
        ///     设置contentType(默认:application/x-www-form-urlencoded)
        /// </summary>
        public string ContentType { get; set; } = "application/x-www-form-urlencoded";

        /// <summary>
        ///     设置accept(默认:*/*)
        /// </summary>
        public string Accept { get; set; } = "*/*";

        public int TimeOut { get; set; } = 30000;

        /// <summary>
        /// </summary>
        public string UserAgent { get; set; } =
            "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.57 Safari/537.36";

        /// <summary>
        ///     接受的语言
        /// </summary>
        public string AcceptLanguage { get; set; }

        /// <summary>
        ///     处理POST请求
        /// </summary>
        /// <param name="url"></param>
        /// <param name="postdata"></param>
        /// <returns></returns>
        public string HttpPost(string url, string postdata)
        {
            var request = CreateWebRequest(url);
            request.Method = "POST";
            if (!string.IsNullOrWhiteSpace(postdata))
            {
                var bytesToPost = Encoding.UTF8.GetBytes(postdata);
                request.ContentLength = bytesToPost.Length;
                using (var requestStream = request.GetRequestStream())
                {
                    requestStream.Write(bytesToPost, 0, bytesToPost.Length);
                    requestStream.Close();
                }
            }

            using (var response = (HttpWebResponse)request.GetResponse())
            {
                using (var sr = new StreamReader(response.GetResponseStream()))
                {
                    var result = sr.ReadToEnd();
                    return result;
                }
            }
        }

        /// <summary>
        ///     带证书的post请求
        /// </summary>
        /// <param name="url"></param>
        /// <param name="postdata"></param>
        /// <param name="cer"></param>
        /// <returns></returns>
        public string HttpPost(string url, string postdata, X509Certificate cer)
        {
            var request = CreateWebRequest(url);
            request.ClientCertificates.Add(cer);
            request.Method = "POST";
            if (!string.IsNullOrWhiteSpace(postdata))
            {
                var bytesToPost = Encoding.UTF8.GetBytes(postdata);
                request.ContentLength = bytesToPost.Length;
                using (var requestStream = request.GetRequestStream())
                {
                    requestStream.Write(bytesToPost, 0, bytesToPost.Length);
                    requestStream.Close();
                }
            }

            using (var response = (HttpWebResponse)request.GetResponse())
            {
                using (var sr = new StreamReader(response.GetResponseStream()))
                {
                    var result = sr.ReadToEnd();
                    return result;
                }
            }
        }

        public UnifiedorderResult HttpPost(string url, object obj, out string result, Func<string, string> serializeStrFunc = null,
            WebRequestDataTypes inputDataType = WebRequestDataTypes.JSON,
            WebRequestDataTypes outDataType = WebRequestDataTypes.JSON)
        {
            string postStr = null;
            switch (inputDataType)
            {
                case WebRequestDataTypes.XML:
                    postStr = XmlHelper.SerializeObject(obj);
                    break;
                default:
                    postStr = JsonConvert.SerializeObject(obj);
                    break;
            }

            if (serializeStrFunc != null)
                postStr = serializeStrFunc(postStr);
            //WeChatPayHelper.LoggerAction?.Invoke("api", string.Format("Pre POST Url:{0},Data:{1}", url, postStr));
            result = HttpPost(url, postStr);
            //WeChatPayHelper.LoggerAction?.Invoke("api", string.Format("POST Url:{0},result:{1}", url, result));
            switch (outDataType)
            {
                case WebRequestDataTypes.XML:
                    return XmlHelper.DeserializeObject<UnifiedorderResult>(result);
                default:
                    return JsonConvert.DeserializeObject<UnifiedorderResult>(result);
            }
        }

        public T HttpPost<T>(string url, string fileName, Stream fileStream, out string result,
            WebRequestDataTypes outDataType = WebRequestDataTypes.JSON) where T : class
        {
            var boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
            var boundarybytes = Encoding.UTF8.GetBytes("\r\n--" + boundary + "\r\n");
            var endbytes = Encoding.UTF8.GetBytes("\r\n--" + boundary + "--\r\n");
            var request = CreateWebRequest(url);
            request.ContentType = "multipart/form-data; boundary=" + boundary;
            request.KeepAlive = true;
            request.AllowAutoRedirect = AllowAutoRedirect;
            request.Method = "POST";
            if (Cookie != null)
                request.CookieContainer = Cookie;
            request.Credentials = CredentialCache.DefaultCredentials;

            using (var requestStream = request.GetRequestStream())
            {
                var headerTemplate =
                    "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: application/octet-stream\r\n\r\n";
                var buffer = new byte[4096];
                var bytesRead = 0;
                requestStream.Write(boundarybytes, 0, boundarybytes.Length);
                var header = string.Format(headerTemplate, fileName, fileName);
                var headerbytes = Encoding.UTF8.GetBytes(header);
                requestStream.Write(headerbytes, 0, headerbytes.Length);
                while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
                    requestStream.Write(buffer, 0, bytesRead);

                requestStream.Write(endbytes, 0, endbytes.Length);
            }

            using (var response = (HttpWebResponse)request.GetResponse())
            {
                using (var sr = new StreamReader(response.GetResponseStream()))
                {
                    result = sr.ReadToEnd();
                }
            }

            switch (outDataType)
            {
                case WebRequestDataTypes.XML:
                    return XmlHelper.DeserializeObject<T>(result);
                default:
                    return JsonConvert.DeserializeObject<T>(result);
            }
        }

        /// <summary>
        ///     带证书的post
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="url"></param>
        /// <param name="obj"></param>
        /// <param name="cer"></param>
        /// <param name="result"></param>
        /// <param name="serializeStrFunc"></param>
        /// <param name="inputDataType"></param>
        /// <param name="outDataType"></param>
        /// <returns></returns>
        public T HttpPost<T>(string url, object obj, X509Certificate2 cer, out string result,
            Func<string, string> serializeStrFunc = null, WebRequestDataTypes inputDataType = WebRequestDataTypes.JSON,
            WebRequestDataTypes outDataType = WebRequestDataTypes.JSON) where T : class
        {
            string postStr = null;
            switch (inputDataType)
            {
                case WebRequestDataTypes.XML:
                    postStr = XmlHelper.SerializeObject(obj);
                    break;
                default:
                    postStr = JsonConvert.SerializeObject(obj);
                    break;
            }

            if (serializeStrFunc != null)
                postStr = serializeStrFunc(postStr);
            //WeChatPayHelper.LoggerAction?.Invoke("api", "postStrs:" + postStr);
            result = HttpPost(url, postStr, cer);
            //WeChatPayHelper.LoggerAction?.Invoke("api", "result:" + result);
            switch (outDataType)
            {
                case WebRequestDataTypes.XML:
                    return XmlHelper.DeserializeObject<T>(result);
                default:
                    return JsonConvert.DeserializeObject<T>(result);
            }
        }

        /// <summary>
        ///     post请求返回html
        /// </summary>
        /// <param name="url"></param>
        /// <param name="postDataStr"></param>
        /// <returns></returns>
        public string HttpPost(string url, Dictionary<string, string> postdata)
        {
            string postDataStr = null;
            if (postdata != null && postdata.Count > 0)
                postDataStr = string.Join("&", postdata.Select(it => it.Key + "=" + it.Value));
            return HttpPost(url, postdata);
        }

        /// <summary>
        ///     get请求获取返回的html
        /// </summary>
        /// <param name="url">无参URL</param>
        /// <param name="querydata">参数</param>
        /// <returns></returns>
        public string HttpGet(string url, Dictionary<string, string> querydata)
        {
            if (querydata != null && querydata.Count > 0)
                url += "?" + string.Join("&", querydata.Select(it => it.Key + "=" + it.Value));
            return HttpGet(url);
        }

        /// <summary>
        ///     get请求获取返回的html
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public string HttpGet(string url)
        {
            var request = CreateWebRequest(url);
            request.Method = "GET";
            // response.Cookies = cookie.GetCookies(response.ResponseUri);
            using (var response = (HttpWebResponse)request.GetResponse())
            {
                using (var sr = new StreamReader(response.GetResponseStream(), Encoding.GetEncoding("utf-8")))
                {
                    var result = sr.ReadToEnd();
                    return result;
                }
            }
        }

        protected HttpWebRequest CreateWebRequest(string url)
        {
            var request = (HttpWebRequest)WebRequest.Create(url);
            if (ProxyAddress != null && ProxyAddress != "")
            {
                var proxy = new WebProxy();
                proxy.Address = new Uri(ProxyAddress); //按配置文件创建Proxy 地置
                if (proxy.Address != null) //如果地址为空，则不需要代理服务器
                {
                    proxy.Credentials = new NetworkCredential(ProxyUser, ProxyKey); //从配置封装参数中创建
                    request.Proxy = proxy;
                }
            }

            request.ContentType = ContentType;
            if (Cookie != null)
                request.CookieContainer = Cookie;
            if (string.IsNullOrEmpty(AcceptLanguage))
            {
                var myWebHeaderCollection = request.Headers;
                myWebHeaderCollection.Add("Accept-Language", AcceptLanguage);
            }

            request.Accept = Accept;
            request.UseDefaultCredentials = true;
            request.UserAgent = UserAgent;
            request.Timeout = TimeOut;
            request.AllowAutoRedirect = AllowAutoRedirect;
            SetCertificatePolicy();
            return request;
        }

        /// <summary>
        ///     POST文件
        /// </summary>
        /// <param name="url"></param>
        /// <param name="file">文件路径</param>
        /// <param name="postdata"></param>
        /// <returns></returns>
        public string HttpUploadFile(string url, string file, Dictionary<string, string> postdata)
        {
            return HttpUploadFile(url, file, postdata, Encoding.UTF8);
        }

        /// <summary>
        ///     POST文件
        /// </summary>
        /// <param name="url"></param>
        /// <param name="file">文件路径</param>
        /// <param name="postdata">参数</param>
        /// <param name="encoding"></param>
        /// <returns></returns>
        public string HttpUploadFile(string url, string file, Dictionary<string, string> postdata, Encoding encoding)
        {
            return HttpUploadFile(url, new[] { file }, postdata, encoding);
        }

        /// <summary>
        ///     POST文件
        /// </summary>
        /// <param name="url"></param>
        /// <param name="files">文件路径</param>
        /// <param name="postdata">参数</param>
        /// <returns></returns>
        public string HttpUploadFile(string url, string[] files, Dictionary<string, string> postdata)
        {
            return HttpUploadFile(url, files, postdata, Encoding.UTF8);
        }

        /// <summary>
        ///     POST文件
        /// </summary>
        /// <param name="url"></param>
        /// <param name="files">文件路径</param>
        /// <param name="postdata">参数</param>
        /// <param name="encoding"></param>
        /// <returns></returns>
        public string HttpUploadFile(string url, string[] files, Dictionary<string, string> postdata, Encoding encoding)
        {
            var boundary = "---------------------------" + DateTime.Now.Ticks.ToString("x");
            var boundarybytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "\r\n");
            var endbytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");


            var request = CreateWebRequest(url);
            request.ContentType = "multipart/form-data; boundary=" + boundary;
            request.Method = "POST";
            request.KeepAlive = true;
            ;
            request.AllowAutoRedirect = AllowAutoRedirect;
            if (Cookie != null)
                request.CookieContainer = Cookie;
            request.Credentials = CredentialCache.DefaultCredentials;

            using (var stream = request.GetRequestStream())
            {
                //1.1 key/value
                var formdataTemplate = "Content-Disposition: form-data; name=\"{0}\"\r\n\r\n{1}";
                if (postdata != null)
                    foreach (var key in postdata.Keys)
                    {
                        stream.Write(boundarybytes, 0, boundarybytes.Length);
                        var formitem = string.Format(formdataTemplate, key, postdata[key]);
                        var formitembytes = encoding.GetBytes(formitem);
                        stream.Write(formitembytes, 0, formitembytes.Length);
                    }

                //1.2 file
                var headerTemplate =
                    "Content-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"\r\nContent-Type: application/octet-stream\r\n\r\n";
                var buffer = new byte[4096];
                var bytesRead = 0;
                for (var i = 0; i < files.Length; i++)
                {
                    stream.Write(boundarybytes, 0, boundarybytes.Length);
                    var header = string.Format(headerTemplate, "file" + i, Path.GetFileName(files[i]));
                    var headerbytes = encoding.GetBytes(header);
                    stream.Write(headerbytes, 0, headerbytes.Length);
                    using (var fileStream = new FileStream(files[i], FileMode.Open, FileAccess.Read))
                    {
                        while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
                            stream.Write(buffer, 0, bytesRead);
                    }
                }

                //1.3 form end
                stream.Write(endbytes, 0, endbytes.Length);
            }

            //2.WebResponse
            var response = (HttpWebResponse)request.GetResponse();
            using (var stream = new StreamReader(response.GetResponseStream()))
            {
                return stream.ReadToEnd();
            }
        }


        /// <summary>
        ///     获得响应中的图像
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public Stream GetResponseImage(string url)
        {
            Stream stream = null;
            try
            {
                var request = CreateWebRequest(url);
                request.KeepAlive = true;
                request.Method = "GET";
                var res = (HttpWebResponse)request.GetResponse();
                stream = res.GetResponseStream();
                return stream;
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        ///     正则获取匹配的第一个值
        /// </summary>
        /// <param name="html"></param>
        /// <param name="pattern"></param>
        /// <returns></returns>
        private string GetStringByRegex(string html, string pattern)
        {
            var re = new Regex(pattern, RegexOptions.IgnoreCase);
            var matchs = re.Matches(html);
            if (matchs.Count > 0)
                return matchs[0].Groups[1].Value;
            return "";
        }

        /// <summary>
        ///     正则验证返回的response是否正确
        /// </summary>
        /// <param name="html">Html内容</param>
        /// <param name="pattern">正则表达式</param>
        /// <returns></returns>
        private bool VerifyResponseHtml(string html, string pattern)
        {
            var re = new Regex(pattern);
            return re.IsMatch(html);
        }

        //注册证书验证回调事件，在请求之前注册
        private void SetCertificatePolicy()
        {
            ServicePointManager.ServerCertificateValidationCallback
                += RemoteCertificateValidate;
        }

        /// <summary>
        ///     远程证书验证，固定返回true
        /// </summary>
        private static bool RemoteCertificateValidate(object sender, X509Certificate cert,
            X509Chain chain, SslPolicyErrors error)
        {
            return true;
        }
    }

    /// <summary>
    ///     请求数据的类型
    /// </summary>
    public enum WebRequestDataTypes
    {
        JSON = 0,
        XML = 1
    }

    public static class XmlHelper
    {
        /// <summary>
        ///     XML序列化
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static string SerializeObject<T>(T obj)
        {
            using (var stream = new MemoryStream())
            {
                var xmlSerial = new XmlSerializer(obj.GetType());
                //序列化对象
                xmlSerial.Serialize(stream, obj);
                stream.Position = 0;
                using (var sr = new StreamReader(stream))
                {
                    return sr.ReadToEnd();
                }
            }
        }

        /// <summary>
        ///     序列化XML不带命名空间和定义
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static string SerializeObjectWithoutNamespace<T>(T obj)
        {
            var sb = new StringBuilder();
            var settings = new XmlWriterSettings
            {
                Encoding = Encoding.UTF8,
                Indent = false,
                OmitXmlDeclaration = true
            };
            using (var xmlWriter = XmlWriter.Create(sb, settings))
            {
                //去除默认命名空间xmlns:xsd和xmlns:xsi
                var xmlSerializerNamespaces = new XmlSerializerNamespaces();
                xmlSerializerNamespaces.Add(string.Empty, string.Empty);
                var serializer = new XmlSerializer(obj.GetType());
                serializer.Serialize(xmlWriter, obj, xmlSerializerNamespaces);
            }

            return sb.ToString();
        }

        /// <summary>
        ///     反序列化
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="stream"></param>
        /// <returns></returns>
        public static T DeserializeObject<T>(Stream stream)
        {
            using (var reader = new StreamReader(stream))
            {
                var str = reader.ReadToEnd();
                str = str.Replace("gb2312", "utf-8");
                var xmlSerial = new XmlSerializer(typeof(T));
                using (var rdr = new StringReader(str))
                {
                    return (T)xmlSerial.Deserialize(rdr);
                }
            }
        }

        /// <summary>
        ///     反序列化
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="input"></param>
        /// <returns></returns>
        public static T DeserializeObject<T>(string input) where T : class
        {
            if (!input.StartsWith("<?xml"))
                input = @"<?xml version=""1.0"" encoding=""utf-8""?>" + input;
            using (var memoryStream = new MemoryStream(Encoding.Default.GetBytes(input)))
            {
                return DeserializeObject<T>(memoryStream);                
            }
        }
    }

    [XmlRoot("xml")]
    [Serializable]
    public abstract class PayOutputBase
    {
        /// <summary>
        ///     业务结果  SUCCESS/FAIL
        /// </summary>
        [XmlElement("result_code")]
        public string ResultCode { get; set; }

        /// <summary>
        ///     错误代码
        /// </summary>
        [XmlElement("err_code")]
        public string ErrCode { get; set; }

        /// <summary>
        ///     错误代码描述
        /// </summary>
        [XmlElement("err_code_des")]
        public string ErrCodeDes { get; set; }


        /// <summary>
        ///     返回状态码
        ///     SUCCESS/FAIL，此字段是通信标识，非交易标识，交易是否成功需要查看result_code来判断
        /// </summary>
        [XmlElement("return_code")]
        public string PayReturnCode { get; set; }

        /// <summary>
        ///     详细结果
        /// </summary>
        [XmlIgnore]
        public string DetailResult { get; set; }

        /// <summary>
        ///     返回信息，返回信息，如非空，为错误原因，签名失败，参数格式校验错误
        /// </summary>
        [XmlElement("return_msg")]
        public string Message { get; set; }

        /// <summary>
        ///     是否支付成功
        /// </summary>
        /// <returns></returns>
        public virtual bool IsSuccess()
        {
            return PayReturnCode == "SUCCESS" && ResultCode == "SUCCESS";
        }

        /// <summary>
        ///     获取错误友好提示
        /// </summary>
        /// <returns></returns>
        public string GetFriendlyMessage()
        {
            return $"{ErrCode ?? ""}：{ErrCodeDes ?? Message}".TrimStart('：');
        }
    }

    [XmlRoot("xml")]
    [Serializable]
    public class UnifiedorderResult : PayOutputBase
    {
        /// <summary>
        ///     交易类型:JSAPI、NATIVE、APP
        /// </summary>
        [XmlElement("trade_type")]
        public string TradeType { get; set; }

        /// <summary>
        ///     微信生成的预支付ID，用于后续接口调用中使用
        /// </summary>
        [XmlElement("prepay_id")]
        public string PrepayId { get; set; }

        /// <summary>
        ///     trade_type为NATIVE时有返回，此参数可直接生成二维码展示出来进行扫码支付
        /// </summary>
        [XmlElement("code_url")]
        public string CodeUrl { get; set; }

        /// <summary>
        ///     微信分配的公众账号ID
        /// </summary>
        [XmlElement("appid")]
        public string AppId { get; set; }

        /// <summary>
        ///     微信支付分配的商户号
        /// </summary>
        [XmlElement("mch_id")]
        public string Mch_Id { get; set; }

        /// <summary>
        ///     微信支付分配的终端设备号
        /// </summary>
        [XmlElement("device_info")]
        public string Device_Info { get; set; }

        /// <summary>
        ///     随机字符串，不长于32 位
        /// </summary>
        [XmlElement("nonce_str")]
        public string NonceStr { get; set; }

        /// <summary>
        ///     签名
        /// </summary>
        [XmlElement("sign")]
        public string Sign { get; set; }
    }

    public class AppPayOutput
    {        
        /// <summary>
        ///     trade_type为NATIVE时有返回，此参数可直接生成二维码展示出来进行扫码支付
        /// </summary>        
        public string CodeUrl { get; set; }

        public string AppId { get; set; }

        public string MchId { get; set; }

        public string TimeStamp { get; set; }

        public string NonceStr { get; set; }

        public string PrepayId { get; set; }

        public string Package { get; set; }

        public string SignType { get; set; }
        public string PaySign { get; internal set; }
    }
}
